package net.jalbum.filters.arty.effects;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.RescaleOp;
import se.datadosen.jalbum.JAFilter;

/**
 * base class with convenience methods for the Arty Filters
 *
 * @author Sarah Schölzel
 */
public abstract class ArtySupport implements JAFilter {

    public static final float STRONG_EFFECT_STRENGTH = 0.8F;
    private static float EFFECT_STRENGTH = 0.60F;

    public BufferedImage filter(BufferedImage bi) {
        int dataType = bi.getSampleModel().getDataType();
        if (dataType != DataBuffer.TYPE_INT) {
            bi = convertType(bi, BufferedImage.TYPE_INT_ARGB);
        }
        return bi;
    }

    public static BufferedImage convertType(BufferedImage src, int type) {
        ColorConvertOp cco = new ColorConvertOp(null);
        BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), type);
        cco.filter(src, dest);
        return dest;
    }

    //from David Fichtmüller
    protected BufferedImage applySaturation(BufferedImage bi, double saturation) {
        final double gamma = 0.25;

        if (saturation > 0) {
            saturation = (100 * Math.pow(saturation - 1, 1 / gamma) / Math.pow(100, 1 / gamma)) + 1;
        } else if (saturation == 0) {
            saturation = 1;
        } else {
            saturation = 1 / ((100 * Math.pow(-saturation + 1, 1 / gamma) / Math.pow(100, 1 / gamma)) + 1);
        }

        final int w = bi.getWidth();
        final int h = bi.getHeight();
        final BufferedImage out = new BufferedImage(w, h, bi.getType());

        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                int rgb = bi.getRGB(x, y);

                // get the rgb-values
                int alpha = (int) ((rgb & 0xff000000l) >> 24);
                int r = ((rgb & 0x00ff0000) >> 16);
                int g = ((rgb & 0x0000ff00) >> 8);
                int b = ((rgb & 0x000000ff));

                // convert to YCbCr
                double Y = r * 0.299 + g * 0.587 + b * 0.114;
                double Cb = r * -0.168736 + g * -0.331264 + b * 0.5;
                double Cr = r * 0.5 + g * -0.418688 + b * -0.081312;

                // apply saturation filter
                Cb = Cb * saturation;
                Cr = Cr * saturation;

                // convert back to RGB
                r = (int) (Y + (Cr * 1.402));
                g = (int) (Y + (Cb * -0.344136) + (Cr * -0.714136));
                b = (int) (Y + (Cb * 1.772));

                // check sizes of return values
                if (alpha > 255) {
                    alpha = 255;
                } else if (alpha < 0) {
                    alpha = 0;
                }
                if (g > 255) {
                    g = 255;
                } else if (g < 0) {
                    g = 0;
                }
                if (r > 255) {
                    r = 255;
                } else if (r < 0) {
                    r = 0;
                }
                if (b > 255) {
                    b = 255;
                } else if (b < 0) {
                    b = 0;
                }

                rgb = ((alpha & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);
                out.setRGB(x, y, rgb);
            }
        }

        return out;

    }

    protected BufferedImage applyBrightnessAndContrast(BufferedImage bi, double brightness, double contrast) {
        final double gamma = 0.25;

        if (contrast > 0) {
            contrast = (100 * Math.pow(contrast - 1, 1 / gamma) / Math.pow(100, 1 / gamma)) + 1;
        } else if (contrast == 0) {
            contrast = 1;
        } else {
            contrast = 1 / ((100 * Math.pow(-contrast + 1, 1 / gamma) / Math.pow(100, 1 / gamma)) + 1);
        }

        RescaleOp rescaleOp = new RescaleOp((float)contrast , (float)brightness, null);
        rescaleOp.filter(bi, bi);  // Source and destination are the same.    
        return bi;        
    }
    
    /**
     * creates a gray image
     *
     * @param bi colored image
     * @return gray scaled image
     */
//    protected BufferedImage getGrayScale(BufferedImage bi) {
//        // the following code is by David Ekholm from the GrayscaleFilter
//        final Graphics2D g = bi.createGraphics();
//        final RenderingHints rhs = g.getRenderingHints();
//
//        final ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
//        final ColorConvertOp theOp = new ColorConvertOp(cs, rhs);
//
//        final BufferedImage dstImg = new BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType());
//        theOp.filter(bi, dstImg);
//
//        return dstImg;
//    }
    protected BufferedImage getGrayScale(BufferedImage bi) {
        final BufferedImage tmp = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
        Graphics g = tmp.getGraphics();
        g.drawImage(bi, 0, 0, null);
        g.dispose();
        final BufferedImage dst = new BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType());
        g = dst.getGraphics();
        g.drawImage(tmp, 0, 0, null);        
        g.dispose();
        return dst;
    }

    public static BufferedImage adjustBorder(BufferedImage bi) {
        return adjustBorder(bi, EFFECT_STRENGTH);
    }

    public static BufferedImage adjustBorder(BufferedImage bi, float strength) {
        BufferedImage borderMask = createSoftBorderMask(bi.getWidth(), bi.getHeight(), strength);
        Graphics2D g = bi.createGraphics();
        g.setComposite(BlendComposite.getInstance(BlendComposite.BlendingMode.MULTIPLY, 0.95f));
        g.drawImage(borderMask, 0, 0, null);

        return bi;
    }

    private static BufferedImage createSoftBorderMask(int width, int height, float strength) {
        if (strength > 1.0f) {
            strength = 1.0f;
        } else if (strength < 0.0f) {
            strength = 0.0f;
        }

        BufferedImage dstImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = dstImg.createGraphics();

        g.setStroke(new BasicStroke(2.0f));
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);

        int bw = width / 4; // borderWidth
        int r = width / 3; // radius
        int colorComponent;

        int i;
        for (i = bw; i >= 0; i--) {
            colorComponent = 255 - (int) (255 * i / bw * strength);
            if (colorComponent < 0) {
                colorComponent = 0;
            }
            g.setColor(new Color(colorComponent, colorComponent, colorComponent));
            g.drawRoundRect(bw / 2 - i, bw / 2 - i, width - bw + 2 * i, height - bw + 2 * i, r, r);
        }
        i = 0;
        g.fillRoundRect(bw / 2 - i, bw / 2 - i, width - bw + 2 * i, height - bw + 2 * i, r, r);

        return dstImg;
    }
    //the following code is by David Fichtmüller from BlurFilterPlugin
    private int passes = 3;
    private int strength = 5;

    protected BufferedImage blurFilter(BufferedImage bi) {
        Pixels pixels = new Pixels(bi, true);

        float r = strength / 100.0f;
        r = r * r * r;

        for (int i = 0; i < passes; i++) {
            pixels.boxBlur(r * 0.1f);
        }

        return pixels.getImage();
    }

    protected void setBlurStrength(final int radius) {
        this.strength = radius;
    }

    private static class Pixels {

        private int width;
        private int height;
        private int[] pixels;
        private int[] newPixels;
        private BufferedImage img;

        private Pixels(BufferedImage in, boolean alwaysCopy) {
            this.width = in.getWidth();
            this.height = in.getHeight();
            newPixels = new int[width * height];

            DataBuffer db = in.getRaster().getDataBuffer();
            if (!(db instanceof DataBufferInt) || alwaysCopy) {
                img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                Graphics g = img.getGraphics();
                g.drawImage(in, 0, 0, null);
                g.dispose();
                db = img.getRaster().getDataBuffer();
            } else {
                img = in;
            }

            DataBufferInt dbi = (DataBufferInt) db;
            pixels = dbi.getData();
        }

        private BufferedImage getImage() {
            return img;
        }

        private void blurAlongXAndRotate(int radius) {
            int[] rSum = new int[width + 1];
            int[] gSum = new int[width + 1];
            int[] bSum = new int[width + 1];

            for (int y = 0; y < height; y++) {
                int rAcc = 0;
                int gAcc = 0;
                int bAcc = 0;

                for (int x = 0; x < width;) {
                    int col = pixels[y * width + x++];

                    rSum[x] = rAcc += (col >> 16) & 0xff;
                    gSum[x] = gAcc += (col >> 8) & 0xff;
                    bSum[x] = bAcc += (col) & 0xff;
                }

                for (int x = 0; x < width; x++) {
                    int from = x - radius;
                    int to = x + radius + 1;
                    if (from < 0) {
                        from = 0;
                    }
                    if (to > width) {
                        to = width;
                    }
                    int len = to - from;

                    int r = (rSum[to] - rSum[from]) / len;
                    int g = (gSum[to] - gSum[from]) / len;
                    int b = (bSum[to] - bSum[from]) / len;

                    newPixels[x * height + y] = r << 16 | g << 8 | b;
                }
            }

            int[] tmp = pixels;
            pixels = newPixels;
            newPixels = tmp;

            int itmp = height;
            height = width;
            width = itmp;
        }

        private void boxBlur(float radius) {
            int size = width < height ? width : height;
            int r = (int) (radius * size);

            // This really works!
            blurAlongXAndRotate(r);
            blurAlongXAndRotate(r);
        }
    }
}
